#include <stdio.h>
#include <stdlib.h>
#include "simulator.h"
#include "alu.h"
#include "typewriter.h"
#include "lineprinter.h"
#include "cardreader.h"
#include "cardpunch.h"
#define LESS 0x80000001
#define EQUAL 0
#define GREATOR 1

int memory[MEM_SIZE] = {0};
int entry = 0; // the pointer to the initial instruction

static int pc = 0; // the pointer to the next instruction

static int regs[10] = {0}; // registers
static char *regNames[10] = {"rA", "rI1", "rI2", "rI3", "rI4", "rI5", "rI6", "rX", "rJ", "rZ"};
static enum {A_REG = 0, X_REG = 7, J_REG, Z_REG} regKind;
static int overflowToggle = 0; // {0, 1 = OVERFLOW}
static int compareIndicator = EQUAL;

static void(*opTable[64])() = {NULL};

// four parts of the instruction
static int AA = 0; // preserve the sign
static int I = 0;
static int F = 0;
static int C = 0;

static int M = 0; // AA + regs[I]

static void printWord(int word) {
	// print the word in the mixal
	if(word & SIGN_BIT_MASK)
		printf("- ");
	else
		printf("+ ");
	for(int i = 0; i < 5; ++i) {
		printf("%d ", (word & 0x3f000000) >> 24);
		word <<= 6;
	}
}

static void printReg(int regIndex) {
	// print the word stored in the register
	if(regIndex < A_REG || regIndex > Z_REG) {
		printf("illegal register index\n");
		return;
	}
	printf("%s: ", regNames[regIndex]);
	printWord(regs[regIndex]);
	printf("\n");
}


static void printInst() {
	// print the instruction executed currently
	int AAsign = AA & SIGN_BIT_MASK;
	int Msign = M & SIGN_BIT_MASK;
	printf("%d => ", pc - 1);
	printf("AA: %c%d, ", AAsign ? '-' : '+', abso(AA));
	printf("I: %d, ", I);
	printf("M: %c%d, ", Msign ? '-' : '+', abso(M));
	printf("F: %d, ", F);
	printf("C: %d\n", C);
}

static void printError(char *errormsg) {
	// print the error message and exit
	printf("%s\n", errormsg);
	exit(1);
}

static void checkMemory() {
	// check whether the memory address 'M 'is legal
	// if not, then print the error message and exit
	if(M < 0 || M >= MEM_SIZE)  
		printError("illegal memory address");
}

static void checkField() {
	// check whether the field 'F' is legal
	// if not, then print the error message and exit
	// assert(F >= 0 && F < 64)
	int L = F / 8; // assert(L >= 0 && L < 8)
	int R = F % 8;	// assert(R >= 0 && R < 8)
	if(L > R || R > 5) 
		printError("illegal field");
}

static void checkOverflow(int ret) {
	// 'ret' is the result of arithmetic computation
	if(ret & OVER_BIT_MASK) {
		overflowToggle = 1;
		printf("set the toggle about overflow\n");
	}
}

static void _checkSize(int ret) {
	// check whether the 'ret' could be represented by two bytes in the mixal
	ret = abso(ret); // clear the sign
	ret = shr(ret, 2);
	if(ret) 
		printError("exceed the range of two bytes in the mixal");
}

static void checkSize(int regIndex, int ret) {
	// 'ret' is the result of arithmetic computation or the word in the memory
	// 'regIndex' is the index of register who accepts the 'ret'
	if(regIndex > A_REG && regIndex < X_REG) {
		// check whether the arithmetic computation has overflowed
		if(ret & OVER_BIT_MASK) 
			printError("critical overflow occur");
		// check whether the 'ret' has exceeded the range
		_checkSize(ret);
	}
}

static int _getV(int content) {
	// return 'content'('F');
	int L = F / 8;
	int R = F % 8;
	int sign = 0;
	if(!L) {
		if(!R)
			return 0;
		++L;
		sign = content & SIGN_BIT_MASK;
	}
	// assert(1 <= L <= R <= 5)
	// take (2:4) to explain
	int ret = abso(content); // clear the sign
	ret = shl(ret, L - 1); // C2C3C4C5~
	ret = shr(ret, L - 1 + 5 - R); // ~~C2C3C4
	ret |= sign; // assign the sign
	return ret;
}

static int getV() {
	// if both the memory address 'M' and the field 'F' are legal, 
	// then return memory['M']('F')
	checkMemory();
	checkField();
	return _getV(memory[M]);
}

static void LOAD() {
	// LD[A1-6X]N?
	int V = getV();
	if(C >= 16)
		V = neg(V); // LD[A1-6X]N
	int regIndex = C % 8;
	// assert(!(V & SIGN_BIT_MASK))
	checkSize(regIndex, V);
	regs[regIndex] = V;
	printReg(regIndex);
}

static void STORE() {
	// ST[A1-6XJZ]
	checkMemory();
	checkField();
	int regIndex = C - 24;
	memory[M] = store(memory[M], F, regs[regIndex]);
	printf("MEMORY[%d]: ", M);
	printWord(memory[M]);
	printf("\n");
}

static void ADD() {
	int V = getV();
	int addRet = add(regs[A_REG], V);
	regs[A_REG] = addRet;
	printReg(A_REG);
	checkOverflow(addRet);
}

static void SUB() {
	int V = getV();
	int subRet = sub(regs[A_REG], V);
	regs[A_REG] = subRet;
	printReg(A_REG);
	checkOverflow(subRet);
}

static void MUL() {	
	int V = getV();
	long long mulRet = imul(regs[A_REG], V);
	regs[X_REG] = mulRet; // LOW
	regs[A_REG] = mulRet >> 32; // HIGH
	printReg(A_REG);
	printReg(X_REG);
}

static void DIV() {
	int V = getV();
	long long divRet = idiv(regs[A_REG], regs[X_REG], V);
	int quotient = divRet >> 32;
	regs[X_REG] = divRet; // remainder	
	regs[A_REG] = quotient;
	printReg(A_REG);
	printReg(X_REG);
	checkOverflow(quotient);
}

static void ENT() {
	// (ENT|ENN|INC|DEC)[A1-6X]
	if(F >= 4) 
		printError("illegal F");
	int src1 = 0, src2 = M;
	int regIndex = C - 48;
	if(F % 2)
		src2 = neg(src2);
	if(F < 2)
		src1 = regs[regIndex];
	int addRet = add(src1, src2);
	checkSize(regIndex, addRet);
	regs[regIndex] = addRet;
	printReg(regIndex);
	checkOverflow(addRet);
}

static void CMP() {
	// CMP[A1-6X]
	int src2 = getV();
	int regIndex = C - 56;
	int src1 = _getV(regs[regIndex]);
	int subRet = clr(sub(src1, src2)); // make -0 become +0;
	printf("compare ");
	printWord(src1);
	printf("with ");
	printWord(src2);
	printf("\n");
	if(!subRet) {	
		compareIndicator = EQUAL;
		printf("set the compare indicator with EQUAL\n");
	}
	else if(subRet < 0) {
		compareIndicator = LESS;
		printf("set the compare indicator with LESS\n");
	}
	else {
		compareIndicator = GREATOR;
		printf("set the compare indicator with GREATOR\n");
	}
}

static void _JMP(int caseIndex, int compare) {
	// 'caseIndex' determines the case branch
	// through 'compare' to check whether the condition of jump has satisfied 
	switch(caseIndex) {
		CASE_0:
		case 0: regs[J_REG] = pc; printReg(J_REG);
		case 1: pc = M; break;
		case 2: {
			if(overflowToggle) {
				overflowToggle = 0;
				printf("reset the toggle about overflow\n");
				goto CASE_0;
			}
			break;
		}
		case 3: {
			if(!overflowToggle)
				goto CASE_0;
			overflowToggle = 0;
			printf("reset the toggle about overflow\n");
			break;
		}
		case 9: if(compare == EQUAL) goto CASE_0; // JLE
		case 4: if(compare == LESS) goto CASE_0; break; // JL
		case 7: if(compare == GREATOR) goto CASE_0; // JGE
		case 5: if(compare == EQUAL) goto CASE_0; break; // JE
		case 6: if(compare == GREATOR) goto CASE_0; break; // JG
		case 8: if(compare != EQUAL) goto CASE_0; break; // JNE
		default: printError("illegal F");
	}
}

static void JMP() {
	_JMP(F, compareIndicator);
}

static void REGJMP() {
	int regIndex = C - 40;
	int regValue = clr(regs[regIndex]); // make -0 become +0
	int compare = LESS;
	if(!regValue)
		compare = EQUAL;
	else if(regValue > 0)
		compare = GREATOR;
	_JMP(F + 4, compare);
}

static long long _SLAX(long long combo) {
	if(M <= 0)
		return -1;
	if(M >= 10)
		return 0;
	return combo << (M * 6);
}

static long long _SRAX(long long combo) {
	if(M <= 0)
		return -1;
	if(M >= 10)
		return 0;
	return combo >> (M * 6);
}

static long long _SLC(long long combo) {
	if(M <= 0)
		return -1;
	int count = M % 10;
	long long mask = 0x0fc0000000000000;
	long long head;
	while(count) {
		head = (combo & mask) >> 54;
		combo <<= 6;
		combo |= head;
		--count;
	}
	return combo;
}

static long long _SRC(long long combo) {
	if(M <= 0)
		return -1;
	int count = M % 10;
	long long mask = 0x3f;
	long long tail;
	while(count) {
		tail = (combo & mask) << 54;
		combo >>= 6;
		combo |= tail;
		--count;
	}
	return combo;
}

static void _SHIFT(long long(*func)(long long)) {
	// handle the shift operation about rAX
	// 'func' is the specific shift operation function
	long long combo = abso(regs[A_REG]); // _A
	combo <<= 30; // _A_
	combo |= abso(regs[X_REG]); // _AX
	combo = func(combo); // _A?X?
	// maybe I was a lazy dog
	regs[X_REG] = store(regs[X_REG], 13, combo & (OVER_BIT_MASK - 1));
	regs[A_REG] = store(regs[A_REG], 13, combo >> 30);
}

static void SLAX() {
	_SHIFT(_SLAX);
	printReg(A_REG);
	printReg(X_REG);
}

static void SRAX() {
	_SHIFT(_SRAX);
	printReg(A_REG);
	printReg(X_REG);
}

static void SLC() {
	_SHIFT(_SLC);
	printReg(A_REG);
	printReg(X_REG);
}

static void SRC() {
	_SHIFT(_SRC);
	printReg(A_REG);
	printReg(X_REG);
}

static void SHIFT() {
	switch(F) {
		case 0: regs[A_REG] = shl(regs[A_REG], M); printReg(A_REG); break;
		case 1: regs[A_REG] = shr(regs[A_REG], M); printReg(A_REG); break;
		case 2: SLAX(); break;
		case 3: SRAX(); break;
		case 4: SLC(); break;
		case 5: SRC(); break;
		default: printError("illegal F");
	}
	if(M < 0) 
		printError("illegal shift count");
}

static void MOVE() {
	int count = F;
	int src = M;
	int dst = clr(regs[1]); // make -0 become +0
	while(count) {
		if(src < 0 || src >= MEM_SIZE) 
			printError("illegal source address");
		if(dst < 0 || dst >= MEM_SIZE) 
			printError("illegal destination address");
		memory[dst] = memory[src];
		printf("MEMORY[%d]: ", dst);
		printWord(memory[dst]);
		printf("\n");
		++dst;
		++src;
		--count;
	}
	_checkSize(dst); // check whether rI1 could accept 'dst'
	regs[1] = dst;
	printReg(1);
}

static void NOP() {
	// do nothing
}

static void deviceClos() {
	// close all the devices
	typewriterClos();
	lineprinterClos();
	cardreaderClos();
	cardpunchClos();
}

static void HLT() {
	deviceClos(); // close all the devices
	printf("program exited normally\n");
	exit(0);
}

static void NUM() {
	long long combo = abso(regs[A_REG]); // _A
	combo <<= 30; // _A_
	combo |= abso(regs[X_REG]); // _AX
	long long mask = 0x0fc0000000000000;
	long long num = 0; // the result of conversion
	int count = 10; // the number of characters
	while(count && !(combo & mask)) {
		// skip the ' '
		combo <<= 6;
		--count;
	}
	char ch;
	while(count) {
		ch = (combo & mask) >> 54;
		ch += ' '; // convert to ascii from mixal
		num *= 10;
		if(ch >= '0' && ch <= '9') 
			num += ch - '0';
		combo <<= 6;
		--count;
	}
	regs[A_REG] = store(regs[A_REG], 13, num);
	printReg(A_REG);
}

static void CHAR() {
	int num = abso(regs[A_REG]);
	long long chars = 0; // the result of conversion
	long long digit;
	int pos = 0; // indicate the position of the digit
	while(num) {
		digit = num % 10;
		chars |= (digit + '0' - ' ') << (pos * 6);
		num /= 10;
		++pos;
	}
	regs[X_REG] = store(regs[X_REG], 13, chars & (OVER_BIT_MASK - 1)); 
	regs[A_REG] = store(regs[A_REG], 13, chars >> 30);
	printReg(A_REG);
	printReg(X_REG);
}

static void TRANS() {
	switch(F) {
		case 0: NUM(); break;
		case 1: CHAR(); break;
		case 2: HLT(); break;
		default: printError("illegal F");
	}
}

static void IN() {
	static void (*deviceIns[5])(int) = {
		cardreaderIn, NULL, NULL, NULL, NULL
	};
	if(F < 16 || F > 20)
		printError("unsupported input device");
	checkMemory();
	deviceIns[F - 16](M);
}

static void OUT() {
	static void (*deviceOuts[5])(int) = {
		NULL, cardpunchOut, lineprinterOut, typewriterOut, NULL
	};
	if(F < 16 || F > 20)
		printError("unsupported output device");
	checkMemory();
	deviceOuts[F - 16](M);
}

static void IOC() {
	static void (*deviceIocs[5])() = {
		cardreaderIoc, cardpunchIoc, lineprinterIoc, typewriterIoc, NULL
	};
	if(F < 16 || F > 20)
		printError("unsupported device");
	deviceIocs[F - 16]();
}

static void _JDEV(int value) {
	static int (*deviceReadys[5])() = {
		cardreaderReady, cardpunchReady, lineprinterReady, typewriterReady, NULL
	};
	if(F < 16 || F > 20)
		printError("unsupported device");
	if(deviceReadys[F - 16]() == value) {
		regs[J_REG] = pc;
		pc = M;
	}
}

static void JRED() {
	_JDEV(1);
}

static void JBUS() {
	_JDEV(0);
}

static void deviceInit() {
	// init all the devices
	typewriterInit();
	lineprinterInit();
	cardreaderInit();
	cardpunchInit();
}

void simulate() {
	pc = entry; // initialize 'pc' with initial instruction address
	// initialize the operation table
	opTable[0] = NOP;
	opTable[1] = ADD;
	opTable[2] = SUB;
	opTable[3] = MUL;
	opTable[4] = DIV;
	opTable[5] = TRANS;
	opTable[6] = SHIFT;
	opTable[7] = MOVE;
	for(int i = 8; i < 24; ++i)
		opTable[i] = LOAD;
	for(int i = 24; i < 34; ++i)
		opTable[i] = STORE;
	opTable[34] = JBUS;
	opTable[35] = IOC;
	opTable[36] = IN;
	opTable[37] = OUT;
	opTable[38] = JRED;
	opTable[39] = JMP;
	for(int i = 40; i < 48; ++i)
		opTable[i] = REGJMP;
	for(int i = 48; i < 56; ++i)
		opTable[i] = ENT;
	for(int i = 56; i < 64; ++i)
		opTable[i] = CMP;
	// init all the devices
	deviceInit();
	// main loop
	while(1) {
		if(pc < 0 || pc >= MEM_SIZE) {
			printf("illegal pc\n");
			exit(1);
		}
		int data = memory[pc++];
		M = AA = shr(data, 3); // preserve the sign
		data = abso(data);
		I = shr(shl(data, 2), 4); 
		F = shr(shl(data, 3), 4);
		C = shr(shl(data, 4), 4);
		if(I) {
			if(I < A_REG || I >= X_REG) 
				printError("illegal I");
			M = add(M, regs[I]);
			_checkSize(M);
		}
		M = clr(M); // make -0 become +0;
		printInst();
		// assert(0 <= C < 64)
debug:
		opTable[C]();
	}
} 
